package cloud.tianai.neuron.common;

import cloud.tianai.neuron.active.impl.Sigmoid;

import java.text.NumberFormat;
import java.util.*;

/**
 * @Author: 天爱有情
 * @date 2022/12/11 14:01
 * @Description 仿 python的NumPy
 */
public class NumPy {
    private static Sigmoid sigmoid = new Sigmoid();

    public static Matrix copy(Matrix data) {
        List<List<Double>> lists = data.getData();
        List<List<Double>> copys = new ArrayList<>(lists.size());
        for (List<Double> list : lists) {
            copys.add(new ArrayList<>(list));
        }
        return new Matrix(copys);
    }

    public static Matrix sin(Matrix data) {
        Matrix result = new Matrix(data.getX(), data.getY());
        for (int x = 0; x < data.getX(); x++) {
            for (int y = 0; y < data.get(x).size(); y++) {
                result.set(x, y, Math.sin(data.get(x, y)));
            }
        }
        return result;
    }

    public static Matrix divide(Matrix matrix, double reduceData, boolean ignoreZero) {
        Matrix result = new Matrix(matrix.getX(), matrix.getY());
        for (int x = 0; x < matrix.getX(); x++) {
            List<Double> yData = matrix.get(x);
            for (int y = 0; y < yData.size(); y++) {
                if (ignoreZero && reduceData == 0) {
                    result.set(x, y, yData.get(y));
                } else {
                    result.set(x, y, yData.get(y) / reduceData);
                }
            }
        }
        return result;
    }

    public static Matrix divide(Matrix matrix, List<Double> yArr, boolean ignoreZero) {
        Matrix result = new Matrix(matrix.getX(), matrix.getY());
        for (int x = 0; x < matrix.getX(); x++) {
            List<Double> yData = matrix.get(x);
            for (int y = 0; y < yData.size(); y++) {
                double v = 0;
                if (ignoreZero && yArr.get(y) == 0) {
                    v = yData.get(y);
                } else {
                    if (!isNaNOrInfinite(yData.get(y)) && !isNaNOrInfinite(yArr.get(y))) {
                        v = yData.get(y) / yArr.get(y);
                    } else {
                        v = yData.get(y);
                    }
                }
                result.set(x, y, v);
            }

        }
        return result;
    }

    public static Matrix divide(Matrix a, Matrix b) {
        if (b.getX() == 1 && b.getY() == 1) {
            return divide(a, b.get(0, 0), true);
        }
        if (b.getY() == 1) {
            List<Double> doubles = b.T().get(0);
            return divide(a, doubles, true);
        }
        Matrix result = new Matrix(a.getX(), b.getY());
        for (int x = 0; x < a.getX(); x++) {
            List<Double> yData = a.get(x);
            for (int y = 0; y < yData.size(); y++) {
                if (b.get(x, y) == 0) {
                    result.set(x, y, yData.get(y));
                } else {
                    result.set(x, y, yData.get(y) / b.get(x, y));
                }
            }
        }
        return result;
    }

    public static Matrix divide(Matrix a, Matrix b, boolean ignoreZero) {
        if (b.getX() == 1 && b.getY() == 1) {
            return divide(a, b.get(0, 0), ignoreZero);
        }
        if (b.getY() == 1) {
            List<Double> doubles = b.T().get(0);
            return divide(a, doubles, ignoreZero);
        }
        Matrix result = new Matrix(a.getX(), b.getY());
        for (int x = 0; x < a.getX(); x++) {
            List<Double> yData = a.get(x);
            for (int y = 0; y < yData.size(); y++) {
                if (ignoreZero && b.get(x, y) == 0) {
                    result.set(x, y, yData.get(y));
                } else {
                    result.set(x, y, yData.get(y) / b.get(x, y));
                }
            }
        }
        return result;
    }


    public static Matrix plus(Matrix a, Matrix b) {
        if (a.getY() != b.getY() && b.getY() == 1) {
            List<Double> doubles = b.T().get(0);
            return sub(a, doubles);
        }
        int columns = Math.max(a.getY(), b.getY());
        Matrix result = new Matrix(a.getX(), columns);
        for (int j = 0; j < a.getX(); j++) {
            List<Double> feature = a.get(j);
            for (int i = 0; i < feature.size(); i++) {
                double d = 0;
                try {
                    d = feature.get(i);
                } catch (Exception e) {
                }
                result.set(j, i, d + b.get(j, i));
            }
        }
        return result;
    }


    public static Matrix sub(Matrix a, Matrix b) {
        if (b.getX() == 1 && b.getY() == 1) {
            return sub(a, b.get(0, 0));
        }
        if (a.getY() != b.getY() && b.getY() == 1) {
            List<Double> doubles = b.T().get(0);
            return sub(a, doubles);
        }
        int columns = Math.max(a.getY(), b.getY());
        Matrix result = new Matrix(a.getX(), columns);
        for (int j = 0; j < a.getX(); j++) {
            List<Double> feature = a.get(j);
            for (int i = 0; i < feature.size(); i++) {
                double d = 0;
                try {
                    d = feature.get(i);
                } catch (Exception e) {
                }
                result.set(j, i, d - b.get(j, i));
            }
        }
        return result;
    }

    public static Matrix sub(double data, Matrix b) {
        Matrix matrix = new Matrix(b.getX(), b.getY());
        for (int j = 0; j < b.getX(); j++) {
            List<Double> feature = b.get(j);
            for (int i = 0; i < feature.size(); i++) {

                matrix.set(j, i, data - b.get(j, i));
            }
        }
        return matrix;
    }

    public static Matrix sub(Matrix m, List<Double> reduceDatas) {
        Matrix result = new Matrix(m.getX(), reduceDatas.size());
        for (int a = 0; a < m.getX(); a++) {
            for (int b = 0; b < reduceDatas.size(); b++) {
                double aa = 0;
                if (b < m.getY()) {
                    aa = m.get(a, b);
                }
                result.set(a, b, aa - reduceDatas.get(b));
            }
        }
        return result;
    }

    public static Matrix sub(Matrix m, Double reduceDatas) {
        Matrix result = new Matrix(m.getX(), m.getY());
        for (int a = 0; a < m.getX(); a++) {
            for (int b = 0; b < m.getY(); b++) {
                result.set(a, b, m.get(a, b) - reduceDatas);
            }
        }
        return result;
    }

    public static Matrix dot(Matrix a, Matrix b) {
        Matrix result = new Matrix(a.getX(), b.getY());
        //根据规律用三个for循环实现矩阵相乘
        for (int i = 0; i < a.getX(); i++) {
            for (int j = 0; j < a.get(i).size(); j++) {
                if (j > b.getX() - 1) {
                    break;
                }
                for (int k = 0; k < b.get(j).size(); k++) {
                    if (Double.valueOf(result.get(i, k) + a.get(i, j) * b.get(j, k)).isNaN()) {
                        System.out.println("===");
                    }
                    result.set(i, k, result.get(i, k) + a.get(i, j) * b.get(j, k));
                    if (result.get(i, k).isNaN()) {
                        System.out.println("==========");
                    }
                }
            }
        }
        return result;
    }

    /**
     * 简单乘法运算
     *
     * @param a
     * @param b
     * @return
     */
    public static Matrix multiply(Matrix a, Matrix b) {
        Matrix result = new Matrix(a.getX(), b.getY());
        for (int i = 0; i < a.getX(); i++) {
            for (int j = 0; j < a.get(i).size(); j++) {
                if (j > b.getX() - 1) {
                    break;
                }
                result.set(i, j, a.get(i, j) * b.get(i, j));
            }
        }
        return result;
    }

    public static Matrix multiply(Matrix a, double b) {
        Matrix result = new Matrix(a.getData());
        //根据规律用三个for循环实现矩阵相乘
        for (int i = 0; i < a.getX(); i++) {
            for (int j = 0; j < a.get(i).size(); j++) {
                result.set(i, j, a.get(i).get(j) * b);
            }
        }
        return result;
    }

    public static Matrix pow(Matrix dataSet, int i) {
//        double[][] result = new double[dataSet1.size()][dataSet1[0].length];
        Matrix result = new Matrix(dataSet.getX(), dataSet.getY());
        for (int x = 0; x < dataSet.size(); x++) {
            for (int y = 0; y < dataSet.get(x).size(); y++) {
                result.set(x, y, Math.pow(dataSet.get(x, y), i));
            }
        }
        return result;
    }

    public static Matrix mean(Matrix features, int i) {
        Matrix means = new Matrix(features.getY(), 1);

        if (i == 0) {
            // 计算每一行的均值
            int columnNum = features.getY();
            for (List<Double> feature : features) {
                for (int i1 = 0; i1 < feature.size(); i1++) {
                    means.set(i1, 0, means.get(i1, 0) + feature.get(i1));
                }
            }
            // 进行计算平均值
            for (int i1 = 0; i1 < means.getX(); i1++) {
                means.set(i1, 0, means.get(i1, 0) / features.getX());
            }
            return means;
        }
        // 计算每一行的均值
        return null;
    }

    public static Matrix std(Matrix features, int i) {
        if (i == 0) {
            Matrix columns = features.T();

            Matrix stds = new Matrix(columns.getX(), 1);
//            double[] stds = new double[columns.length];
            for (int i1 = 0; i1 < columns.size(); i1++) {
                stds.set(i1, 0, std(columns.get(i1)));
            }
            return stds;
        }

        // 计算每一行的均值
        return null;
    }

    public static double std(List<Double> x) {
        int m = x.size();
        double sum = 0;
        //求和
        for (int i = 0; i < m; i++) {
            sum += x.get(i);
        }
        //求平均值
        double dAve = sum / m;
        double dVar = 0;
        //求方差
        for (int i = 0; i < m; i++) {
            double pow = Math.pow(x.get(i) - dAve, 2);
            if (!isNaNOrInfinite(pow)) {
                dVar += pow;
            } else {
                dVar += x.get(i) - dAve;
            }
        }
        return Math.sqrt(dVar / m);
    }

    public static final boolean isNaNOrInfinite(final double v) {
        return Double.isNaN(v) || (v == Double.POSITIVE_INFINITY)
                || (v == Double.NEGATIVE_INFINITY);
    }

    public static void concatenate(Matrix target, Matrix data) {
        for (int x = 0; x < data.getX(); x++) {
            for (int y = 0; y < data.getY(); y++) {
                target.get(x).add(data.get(x, y));
            }
        }
    }

    public static Matrix hstack(Matrix... aArr) {
        Matrix result = new Matrix(aArr[0].getX(), 0);
        for (Matrix a : aArr) {
            for (int i = 0; i < a.size(); i++) {
                for (Double aDouble : a.get(i)) {
                    result.get(i).add(aDouble);
                }
            }
        }
        return result;
    }

    public static List<Matrix> split(Matrix dataSet, int num) {
        List<Matrix> result = new ArrayList<>(num);
        List<List<List<Double>>> dataSplits = new ArrayList<>(num);
        for (int i = 0; i < num; i++) {
            dataSplits.add(new ArrayList<>(dataSet.size()));
        }
        for (int x = 0; x < dataSet.size(); x++) {
            List<Double> doubles = dataSet.get(x);
            int firstIndex = 0;
            for (int i = 0; i < doubles.size(); i++) {
                if ((i + 1) % num == 0) {
                    firstIndex++;
                }
                if (dataSplits.size() - 1 < firstIndex) {
                    dataSplits.add(new ArrayList<>());
                }
                if (dataSplits.get(firstIndex).size() - 1 < x) {
                    dataSplits.get(firstIndex).add(new ArrayList<>());
                }
                dataSplits.get(firstIndex).get(x).add(doubles.get(i));
            }
        }
        for (List<List<Double>> dataSplit : dataSplits) {
            result.add(new Matrix(dataSplit));
        }

        return result;
    }

    public static Matrix unique(Matrix data) {
        Matrix result = new Matrix(0, data.getY());
        Set<List<Double>> sets = new HashSet<>();
        for (List<Double> datum : data) {
            if (sets.add(datum)) {
                result.add(datum);
            }
        }
        return result;
    }

    public static Matrix sigmoid(Matrix data) {

        Matrix result = new Matrix(data.getX(), data.getY());
        for (int x = 0; x < data.size(); x++) {
            for (int y = 0; y < data.get(x).size(); y++) {
                result.set(x, y, sigmoid.calc(data.get(x, y)));
            }
        }
        return result;

    }

    public static double sigmoid(double data) {
        return sigmoid.calc(data);
    }

    public static Matrix oneOrZero(Matrix data, Double flag) {
        Matrix result = new Matrix(data.getX(), data.getY());
        for (int x = 0; x < data.size(); x++) {
            for (int y = 0; y < data.get(x).size(); y++) {
                if (data.get(x, y).equals(flag)) {
                    result.set(x, y, 1d);
                }
            }
        }
        return result;
    }

    public static Matrix ofColumn(List<Double> doubles) {
        List<List<Double>> datas = new ArrayList<>(doubles.size());
        for (Double aDouble : doubles) {
            ArrayList<Double> data = new ArrayList<>(1);
            data.add(aDouble);
            datas.add(data);
        }
        return new Matrix(datas);
    }

    public static Matrix ofRow(List<Double> doubles) {
        Matrix matrix = new Matrix(0, 0);
        matrix.add(doubles);
        return matrix;
    }

    public static Matrix ofColumn(double[] doubles) {
        return ofColumn(arrayToList(doubles));
    }

    public static Matrix ofRow(double[] doubles) {
        return ofRow(arrayToList(doubles));
    }

    public static List<Double> arrayToList(double[] arr) {
        List<Double> result = new ArrayList<>(arr.length);
        for (double v : arr) {
            result.add(v);
        }
        return result;
    }

    public static List<List<Double>> arrayToList2D(double[][] arr) {
        List<List<Double>> result = new ArrayList<>(arr.length);
        for (double[] v : arr) {
            result.add(arrayToList(v));
        }
        return result;
    }

    public static double[] ListArray(List<Double> arr) {
        double[] result = new double[arr.size()];
        for (int i = 0; i < arr.size(); i++) {
            result[i] = arr.get(i);
        }
        return result;
    }

    public static double[][] ListArray2D(List<List<Double>> arr) {
        double[][] result = new double[arr.size()][];
        for (int i = 0; i < arr.size(); i++) {
            result[i] = ListArray(arr.get(i));
        }
        return result;
    }


    public static Matrix log(Matrix data) {
        Matrix result = new Matrix(data.getX(), data.getY());
        for (int x = 0; x < data.size(); x++) {
            for (int y = 0; y < data.get(x).size(); y++) {
                if (data.get(x, y) != 0) {
                    result.set(x, y, Math.log(data.get(x, y)));
                } else {
                    result.set(x, y, data.get(x, y));
                }
            }
        }
        return result;
    }
    public static double log(double data) {
        return  Math.log(data);
    }

    private static double range(double data) {
        NumberFormat f = NumberFormat.getNumberInstance();
        f.setMaximumFractionDigits(5);
        return Double.parseDouble(f.format(data));
    }

    public static Matrix min(Matrix matrix, int i) {
        ArrayList<Double> list = new ArrayList<>();
        if (i == 0) {
            matrix = matrix.T();
        }
        for (List<Double> doubles : matrix) {
            Double aDouble = doubles.stream().min(Comparator.comparingDouble(c -> c)).get();
            list.add(aDouble);
        }
        return ofRow(list);
    }

    public static Matrix max(Matrix matrix, int i) {
        ArrayList<Double> list = new ArrayList<>();
        if (i == 0) {
            matrix = matrix.T();
        }
        for (List<Double> doubles : matrix) {
            Double aDouble = doubles.stream().max(Comparator.comparingDouble(c -> c)).get();
            list.add(aDouble);
        }
        return ofRow(list);
    }


    public static Matrix minMax(Matrix matrix, int i) {
        Matrix result = new Matrix(0, 0);
        ArrayList<Double> minList = new ArrayList<>();
        ArrayList<Double> maxList = new ArrayList<>();
        if (i == 0) {
            matrix = matrix.T();
        }
        for (List<Double> doubles : matrix) {
            Double min = doubles.get(0);
            Double max = doubles.get(0);
            for (int i1 = 1; i1 < doubles.size(); i1++) {
                Double d = doubles.get(i1);
                if (d < min) {
                    min = d;
                }
                if (d > max) {
                    max = d;
                }
            }
            minList.add(min);
            maxList.add(max);
        }
        result.add(minList);
        result.add(maxList);
        return result;
    }

    public static Double intercept(Double d) {
        return d;
    }

}
